//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Mapping Application Block
//===============================================================================

using System;
using System.Globalization;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Practices.EnterpriseLibrary.Configuration.Design;
using EntLibContrib.Mapping.Properties;

namespace EntLibContrib.Mapping.Configuration.Design.Generators
{
  /// <summary>
	/// MapperBuilder builds mappers from classes held within an assembly
  /// </summary>
  public static class MapperBuilder
  {
		#region Constants
		private const string defaultTypeColumnName = "Type";
		#endregion

		#region Public Static Methods
		/// <summary>
		/// Builds the Mappers from the specified assembly file name.
		/// </summary>
		/// <param name="assemblyFileName">Name of the assembly file.</param>
		/// <returns>list of mapper nodes</returns>
		public static Dictionary<string, MapperBaseNode> Build(string assemblyFileName)
		{
			var assembly = LoadAssembly(assemblyFileName);
			var mappers = new Dictionary<string, MapperBaseNode>();

			// get the public types from the assembly
			Type[] publicTypes = assembly.GetExportedTypes();

			// cycle through the list of types inspecting the custom attributes
			foreach (Type publicType in publicTypes)
			{
				BuildMapperNode(mappers, publicType);
			}

			return mappers;
		}
		#endregion

		#region Private Static Methods
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")]
		private static Assembly LoadAssembly(string assemblyFileName)
		{
			return Assembly.LoadFrom(assemblyFileName);
		}

		private static void BuildMapperNode(Dictionary<string, MapperBaseNode> mappers, Type publicType)
		{
			// look for basic mapper attribute
			BuildBasicMapperNode(mappers, publicType);
			// look for super mapper attribute
			BuildSuperMapperNode(mappers, publicType);
		}

		private static void BuildBasicMapperNode(Dictionary<string, MapperBaseNode> mappers, Type publicType)
		{
			// get the basic mapper attributes from the type
			MapperAttribute[] typeAttributes = (MapperAttribute[])publicType.GetCustomAttributes(typeof(MapperAttribute), true);
			if (typeAttributes.Length > 0)
			{
				// there should only be one
				var typeAttribute = typeAttributes[0];
				// build the mapper name
				var mapperName = typeAttribute.Name;
				if (string.IsNullOrEmpty(mapperName))
					mapperName = publicType.Name;

				// search for an existing mapper node
				BasicMapperNode mapperNode = null;
				if (mappers.ContainsKey(mapperName))
					throw new MapperBuilderException(String.Format(CultureInfo.CurrentCulture, InternalResources.DuplicateMapperName, mapperName));
				else
				{
					// build the node data
					var mapperData = new BasicMapperData(mapperName, typeAttribute.TableName, publicType.AssemblyQualifiedName);
					// build the node
					mapperNode = new BasicMapperNode(mapperData);
					// add the node to the dictionary
					mappers.Add(mapperName, mapperNode);
				}

				BuildMapNodes(publicType, mapperNode);
			}
		}

		private static void BuildSuperMapperNode(Dictionary<string, MapperBaseNode> mappers, Type publicType)
		{
			// get the super mapper attributes from the type
			SuperMapperAttribute[] typeAttributes = (SuperMapperAttribute[])publicType.GetCustomAttributes(typeof(SuperMapperAttribute), true);
			if (typeAttributes.Length > 0)
			{
				// there should only be one
				var typeAttribute = typeAttributes[0];
				// build the mapper name
				var mapperName = typeAttribute.Name;
				if (string.IsNullOrEmpty(mapperName))
				{
					Type baseType = publicType;
					// get the default name from the parent type
					while (baseType.BaseType != typeof(Object))
					{
						baseType = baseType.BaseType;
					}
					mapperName = baseType.Name;
				}
				// build the type column name
				var typeColumnName = typeAttribute.TypeColumnName;
				if (string.IsNullOrEmpty(typeColumnName))
					typeColumnName = defaultTypeColumnName;

				// search for an existing mapper node
				SuperMapperNode mapperNode = null;
				if (mappers.ContainsKey(mapperName))
				{
					mapperNode = mappers[mapperName] as SuperMapperNode;
					if (mapperNode == null)
						throw new MapperBuilderException(String.Format(CultureInfo.CurrentCulture, InternalResources.MismatchedSuperMapperType, mapperName));
					else
						BuildSuperType(publicType, typeAttribute, mapperNode);
				}
				else
				{
					// build the node data
					var mapperData = new SuperMapperData(mapperName, typeAttribute.TableName, typeColumnName);
					// build the node
					mapperNode = new SuperMapperNode(mapperData);
					// build a super type
					BuildSuperType(publicType, typeAttribute, mapperNode);
					// add the node to the dictionary
					mappers.Add(mapperName, mapperNode);
				}

				BuildMapNodes(publicType, mapperNode);
			}
		}

		private static void BuildSuperType(Type publicType, SuperMapperAttribute typeAttribute, SuperMapperNode mapperNode)
		{
			// build a super type if a type column value exists
			if (!string.IsNullOrEmpty(typeAttribute.TypeColumnValue))
			{
				var superType = new SuperType(typeAttribute.TypeColumnValue, publicType.AssemblyQualifiedName);
				mapperNode.SuperTypes.Add(superType);
			}
		}

		private static void BuildMapNodes(Type publicType, MapperBaseNode mapperNode)
		{
			// get the public fields from the type
			FieldInfo[] publicFields = publicType.GetFields(BindingFlags.Instance | BindingFlags.Public);
			// cycle through the list of fields inspecting the custom attributes
			foreach (FieldInfo publicField in publicFields)
			{
				BuildMapNode(mapperNode, publicField);
			}

			// get the public properties from the type
			PropertyInfo[] publicProperties = publicType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
			// cycle through the list of properties inspecting the custom attributes
			foreach (PropertyInfo publicProperty in publicProperties)
			{
				BuildMapNode(mapperNode, publicProperty);
			}
		}

		private static void BuildMapNode(MapperBaseNode mapperNode, MemberInfo publicMember)
		{
			// get the map attributes from the member
			MapAttribute[] mapAttributes = (MapAttribute[])publicMember.GetCustomAttributes(typeof(MapAttribute), true);
			if (mapAttributes.Length > 0)
			{
				// there should only be one
				var mapAttribute = mapAttributes[0];
				// build the map name
				var mapName = publicMember.Name;

				// search for an existing map node
				if (!mapperNode.Nodes.Contains(mapName))
				{
					// build the node data
					var mapData = new MapData(publicMember.Name, mapAttribute.ColumnName);
					// build the node
					var mapNode = new MapNode(mapData);
					// add the node to the mapper
					mapperNode.AddNode(mapNode);
				}
			}
		}
		#endregion
	}
}
